package com.adrninistrator.jacg.util;

import com.adrninistrator.javacg.common.JavaCGConstants;
import com.adrninistrator.javacg.util.JavaCGFileUtil;
import com.adrninistrator.javacg.util.JavaCGUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author adrninistrator
 * @date 2021/6/18
 * @description:
 */

public class JACGFileUtil {

    private static final Logger logger = LoggerFactory.getLogger(JACGFileUtil.class);

    private static final Pattern JAR_VERSION_PATTERN = Pattern.compile("-[0-9]");

    public static String readFile2String(String filePath) {
        try (InputStream inputStream = JavaCGFileUtil.getFileInputStream(filePath)) {
            return readInputStream2String(inputStream);
        } catch (Exception e) {
            logger.error("error {} ", filePath, e);
            return null;
        }
    }

    public static String readInputStream2String(InputStream inputStream) throws IOException {
        return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
    }

    public static String readFile2String(File file) throws IOException {
        return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
    }

    /**
     * 查找指定目录下指定类型的文件，不遍历子目录
     *
     * @param dirPath
     * @param fileExt
     * @return
     */
    public static List<File> findFileInCurrentDir(String dirPath, String... fileExt) {
        File dir = new File(dirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            logger.error("目录不存在，或不是目录 {}", dirPath);
            return Collections.emptyList();
        }

        File[] files = dir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            return Collections.emptyList();
        }

        List<File> fileList = new ArrayList<>(files.length);
        for (File file : files) {
            if (file.isFile() && StringUtils.endsWithAny(file.getName(), fileExt)) {
                fileList.add(file);
            }
        }
        return fileList;
    }

    /**
     * 查找指定目录下指定类型的文件，不遍历子目录
     *
     * @param dirPath
     * @param fileExt
     * @return
     */
    public static List<String> findFilePathInCurrentDir(String dirPath, String... fileExt) {
        List<File> fileList = findFileInCurrentDir(dirPath, fileExt);
        List<String> filePathList = new ArrayList<>(fileList.size());
        for (File file : fileList) {
            filePathList.add(file.getAbsolutePath());
        }
        return filePathList;
    }

    /**
     * 查找指定目录下的目录，不遍历子目录
     *
     * @param dirPath
     * @return
     */
    public static List<File> findDirInCurrentDir(String dirPath) {
        File dir = new File(dirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            logger.warn("目录不存在，或不是目录 {}", dirPath);
            return Collections.emptyList();
        }

        File[] files = dir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            return Collections.emptyList();
        }

        List<File> fileList = new ArrayList<>(files.length);
        for (File file : files) {
            if (file.isDirectory()) {
                fileList.add(file);
            }
        }
        return fileList;
    }

    /**
     * 查找指定目录下的目录，不遍历子目录
     *
     * @param dirPath
     * @return
     */
    public static List<String> findDirPathInCurrentDir(String dirPath) {
        List<File> fileList = findDirInCurrentDir(dirPath);
        List<String> filePathList = new ArrayList<>(fileList.size());
        for (File file : fileList) {
            filePathList.add(file.getAbsolutePath());
        }
        return filePathList;
    }

    /**
     * 将指定的一个或多个文本类型的源文件合并为目录文件
     *
     * @param destFilePath 目标文件路径
     * @param srcFileList  源文件列表
     * @param append       是否追加文件内容
     * @return
     */
    public static boolean combineTextFile(String destFilePath, List<File> srcFileList, boolean append) {
        if (JavaCGUtil.isCollectionEmpty(srcFileList)) {
            logger.error("指定的源文件列表为空");
            return false;
        }

        try (BufferedWriter writer = JavaCGFileUtil.genBufferedWriter(destFilePath, append)) {
            for (File file : srcFileList) {
                // 拷贝指定文件的内容
                if (!copyFileContent(writer, file)) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            logger.error("error {} ", destFilePath, e);
            return false;
        }
    }

    // 拷贝指定文件的内容
    public static boolean copyFileContent(BufferedWriter writer, File file) {
        try (BufferedReader br = JavaCGFileUtil.genBufferedReader(file)) {
            String line;
            while ((line = br.readLine()) != null) {
                writer.write(line + JavaCGConstants.NEW_LINE);
            }
            writer.write(JavaCGConstants.NEW_LINE);
            return true;
        } catch (Exception e) {
            logger.error("error {} ", file.getAbsolutePath(), e);
            return false;
        }
    }

    public static boolean isFileExists(String filePath) {
        File file = new File(filePath);
        return file.exists() && file.isFile();
    }

    public static String getFileMd5(String filePath) {
        try (InputStream input = new FileInputStream(filePath)) {
            return DigestUtils.md5Hex(input);
        } catch (Exception e) {
            logger.error("error {} ", filePath, e);
            return null;
        }
    }

    /**
     * 获得文件修改时间的字符串形式，精度到秒
     *
     * @param filePath
     * @return
     */
    public static String getFileLastModifiedTime(String filePath) {
        try {
            long lastModified = new File(filePath).lastModified();
            Date date = new Date(lastModified);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return sdf.format(date);
        } catch (Exception e) {
            logger.error("error ", e);
            return "";
        }
    }

    /**
     * 生成文件
     *
     * @param filePath 文件路径
     * @return true: 文件已存在，或创建成功，false: 文件不存在且创建失败
     */
    public static boolean createNewFile(String filePath) {
        File file = new File(filePath);
        if (file.exists() && file.isFile()) {
            logger.info("文件已存在1 {}", filePath);
            return true;
        }

        try {
            // 判断文件所在目录是否存在，若不存在则创建
            File dir = file.getParentFile();
            if (!JavaCGFileUtil.isDirectoryExists(dir)) {
                return false;
            }

            Files.createFile(file.toPath());
            logger.info("生成文件 {}", filePath);
            return true;
        } catch (FileAlreadyExistsException e) {
            logger.warn("文件已存在2 {}", filePath);
            return true;
        } catch (IOException e) {
            logger.error("error ", e);
            return false;
        }
    }

    /**
     * 从目录中查找需要处理的文件
     *
     * @param dirPath         需要查找的目录
     * @param subDirPathSet   保存查找到的目录，可为空
     * @param subFilePathList 保存查找到的文件列表
     * @param fileExts        需要查找的文件后缀，可为空
     */
    public static void searchDir(String dirPath, Set<String> subDirPathSet, List<String> subFilePathList, String... fileExts) {
        File dir = new File(dirPath);
        File[] files = dir.listFiles();
        if (files == null) {
            return;
        }

        for (File file : files) {
            if (file.isDirectory()) {
                // 目录，递归
                searchDir(file.getAbsolutePath(), subDirPathSet, subFilePathList, fileExts);
            } else {
                // 文件
                String filePath = file.getAbsolutePath();
                if (fileExts == null || checkFileExt(filePath, fileExts)) {
                    // 若未指定文件后缀，则允许任意文件后缀；若有指定文件后缀，则需要判断是否符合预期
                    if (subDirPathSet != null) {
                        subDirPathSet.add(dirPath);
                    }
                    subFilePathList.add(filePath);
                }
            }
        }
    }

    /**
     * 判断文件后缀是否符合预期
     *
     * @param filePath
     * @param fileExts
     * @return
     */
    public static boolean checkFileExt(String filePath, String... fileExts) {
        if (fileExts == null) {
            // 未指定文件后缀时认为不符合
            return false;
        }

        for (String fileExt : fileExts) {
            if (StringUtils.endsWithIgnoreCase(filePath, fileExt)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据文件路径获取文件名
     *
     * @param filePath
     * @return
     */
    public static String getFileNameFromPath(String filePath) {
        if (filePath == null) {
            return null;
        }

        String tmpFilePath = replaceFilePathSeparator(filePath);
        // 不使用StringUtils.substringAfterLast，因为当没有/时结果为空
        int lastSeparatorIndex = tmpFilePath.lastIndexOf("/");
        if (lastSeparatorIndex == -1) {
            return filePath;
        }
        return tmpFilePath.substring(lastSeparatorIndex + 1);
    }

    /**
     * 获取不包含后缀的文件名
     *
     * @param fileName 文件名
     * @return
     */
    public static String getFileNameWithOutExt(String fileName) {
        return getFileNameWithOutExt(fileName, JavaCGConstants.FLAG_DOT);
    }

    /**
     * 获取文件名后缀
     *
     * @param fileName 文件名
     * @return
     */
    public static String getFileNameExt(String fileName) {
        if (fileName == null) {
            return null;
        }

        // 不使用StringUtils.substringBeforeLast，因为当没有指定字符串时结果为空
        int lastDotIndex = fileName.lastIndexOf(JavaCGConstants.FLAG_DOT);
        if (lastDotIndex == -1) {
            return "";
        }
        return fileName.substring(lastDotIndex);
    }

    /**
     * 获取不包含后缀的文件名
     *
     * @param fileName    文件名
     * @param fileExtFlag 文件后缀标志，默认使用.
     * @return
     */
    public static String getFileNameWithOutExt(String fileName, String fileExtFlag) {
        if (fileName == null || fileExtFlag == null) {
            return null;
        }

        // 不使用StringUtils.substringBeforeLast，因为当没有指定字符串时结果为空
        int lastDotIndex = fileName.lastIndexOf(fileExtFlag);
        if (lastDotIndex == -1) {
            return fileName;
        }
        return fileName.substring(0, lastDotIndex);
    }

    /**
     * 获取jar文件名不包含后缀及版本号的内容
     *
     * @param jarFileName
     * @return
     */
    public static String getJarFileHead(String jarFileName) {
        if (jarFileName == null) {
            return null;
        }
        String fileNameWithOutExt = getFileNameWithOutExt(jarFileName);

        Matcher matcher = JAR_VERSION_PATTERN.matcher(fileNameWithOutExt);
        if (!matcher.find()) {
            // 未找到版本号
            return fileNameWithOutExt;
        }
        return fileNameWithOutExt.substring(0, matcher.start());
    }

    /**
     * 将文件路径中的\替换为/
     *
     * @param filePath
     * @return
     */
    public static String replaceFilePathSeparator(String filePath) {
        return StringUtils.replace(filePath, "\\", "/");
    }

    /**
     * 判断文件路径中是否包含目录分隔符\、/
     *
     * @param filePath
     * @return
     */
    public static boolean checkFilePathContainsSeparator(String filePath) {
        return StringUtils.containsAny(filePath, "\\", "/");
    }

    /**
     * 获取文件所在目录名
     *
     * @param filePath
     * @return
     */
    public static String getFileParentDirName(String filePath) {
        if (filePath == null) {
            return null;
        }

        String tmpFilePath = replaceFilePathSeparator(filePath);
        String[] array = StringUtils.splitPreserveAllTokens(tmpFilePath, "/");
        if (array.length < 2) {
            return null;
        }

        // 使用分隔符进行分隔后，返回倒数第二个数据
        return array[array.length - 2];
    }

    /**
     * 重命名文件
     *
     * @param oldFilePath
     * @param newFilePath
     * @return
     */
    public static boolean renameFile(String oldFilePath, String newFilePath) {
        File oldFile = new File(oldFilePath);
        if (!oldFile.exists() || !oldFile.isFile()) {
            logger.error("旧文件不存在或不是文件 {}", oldFilePath);
            return false;
        }

        File newFile = new File(newFilePath);
        if (newFile.exists()) {
            logger.error("新文件已存在 {}", newFilePath);
            return false;
        }

        // 判断新文件所在目录是否存在，若不存在则创建
        File newDir = newFile.getParentFile();
        if (!JavaCGFileUtil.isDirectoryExists(newDir)) {
            return false;
        }

        if (!oldFile.renameTo(newFile)) {
            logger.error("重命名文件失败 {} {}", oldFilePath, newFilePath);
            return false;
        }

        logger.info("重命名文件 {} {}", oldFilePath, newFilePath);
        return true;
    }

    /**
     * 拷贝文件
     *
     * @param srcFile
     * @param destFile
     * @return
     */
    public static boolean copyFile(File srcFile, File destFile) {
        try {
            Files.copy(srcFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
            return true;
        } catch (Exception e) {
            logger.error("error ", e);
            return false;
        }
    }

    /**
     * 根据jar包中的文件路径获取文件名
     *
     * @param filePath
     * @return
     */
    public static String getFileNameFromPathInJar(String filePath) {
        return StringUtils.substringAfterLast(filePath, "/");
    }

    /**
     * 将输入流内容保存到文件，不关闭输入流（避免jar/war包无法再遍历）
     *
     * @param inputStream
     * @param file
     * @return
     */
    public static boolean saveInputToFileNoClose(InputStream inputStream, File file) {
        String dirPath = file.getParent();
        if (!JavaCGFileUtil.isDirectoryExists(dirPath, true)) {
            return false;
        }

        try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
            byte[] data = new byte[8192];
            int len;
            while ((len = inputStream.read(data)) > 0) {
                out.write(data, 0, len);
            }
            return true;
        } catch (Exception e) {
            logger.error("error ", e);
            return false;
        }
    }

    private JACGFileUtil() {
        throw new IllegalStateException("illegal");
    }
}
